package the_Crash;

import java.util.ArrayList;
import java.util.List;

import repast.simphony.context.Context;
import repast.simphony.engine.schedule.ScheduledMethod;
import repast.simphony.query.space.grid.GridCell;
import repast.simphony.query.space.grid.GridCellNgh;
import repast.simphony.random.RandomHelper;
import repast.simphony.space.continuous.ContinuousSpace;
import repast.simphony.space.grid.Grid;
import repast.simphony.space.grid.GridPoint;

public class SourceSink extends BaseSimEntity {

	private static double probabilitySum;
	private static ArrayList<SourceSink> listOfSourceSinks;
	private static long numArrivals;
	private static double avgArrivalEfficiency;
	
	private double spawnChance;
	private StreetCell spawnLocation;
	private Context<Object> context;
	private boolean justSpawned;
	private boolean justAbsorbed;
	
	public static void globalReset() {
		probabilitySum=0;
		listOfSourceSinks = new ArrayList<SourceSink>();
		numArrivals=0;
		avgArrivalEfficiency=0;
	}
	
	public SourceSink(double spawnChance, Context<Object> context, GridPoint location, Grid<Object> grid, ContinuousSpace<Object> space) {
		super(location, grid,space);
		probabilitySum+=spawnChance;		
		this.spawnChance=spawnChance;
		this.context=context;
		listOfSourceSinks.add(this);
	}
	
	
	/*
	 * This method is needed because the source-sink must be added to the context first, then moved to its location, and only then can 
	 * its headings and values be initialized
	 */
	
	public void initialize(){
		this.moveToLocation();
		this.spawnLocation = findSpawnLocation();
	}
	
	//finds the street outlet location of the SourceSink
	private StreetCell findSpawnLocation()
	{
		GridPoint location = grid.getLocation(this);
		GridCellNgh<StreetCell> nghCreator = new GridCellNgh<>(grid, location, StreetCell.class, 2, 2);
		List<GridCell<StreetCell>> gridCells = nghCreator.getNeighborhood(false);
		StreetCell closestStreet = null;
		double minDist=2;
		double dist;
		for(GridCell<StreetCell> cell: gridCells)
		{
			for(StreetCell streetCell: cell.items())
			{
				dist = space.getDistance(space.getLocation(streetCell), space.getLocation(this));
				if(dist<minDist)
				{
					closestStreet = streetCell;
					minDist=dist;
				}
			}
		}
		return closestStreet;
	}
	
	//checks if the street is clear to spawn
	private boolean clearSpawn()
	{
		for(Object o: grid.getObjectsAt(spawnLocation.getLocation().getX(), spawnLocation.getLocation().getY()))
		{
			if(o instanceof Car)
				return false;
		}
		for(Object o: grid.getObjectsAt(spawnLocation.getPreviousCell().getLocation().getX(), spawnLocation.getPreviousCell().getLocation().getY()))
		{
			if(o instanceof Car)
				return false;
		}
		
		return true;
	}
	
	//finds a sink based on the activity weights
	private SourceSink getSink()
	{
		double rand = RandomHelper.nextDoubleFromTo(0, probabilitySum);
		for(SourceSink s: listOfSourceSinks)
		{
			rand-=s.spawnChance;
			if(rand<0)
				return s;
		}
		return null;
	}


	//checks for spawning a new car every tick with a probability equal to its activity weight
	@ScheduledMethod (start = 1, interval = 1)
	public void step()
	{		
		// Check if the source to allowed to make cars
		justSpawned=false;
		justAbsorbed=false;
		if (ParameterResolver.getMaximumCars() <= CityBuilder.globalCarCount) return; // Can't make cars. Enough cars in the world
		Car car;
		if(RandomHelper.nextDoubleFromTo(0, 1)<spawnChance && clearSpawn())
		{
			justSpawned=true;
			if(RandomHelper.nextIntFromTo(1, 100)<=ParameterResolver.getSmartCar())
				car = new SmartCar(getSink(), this, 1, 0.25, 0.5, spawnLocation.getLocation(), grid, space);
			else
				car = new DumbCar(getSink(), this, 1, 0.25, 0.5, spawnLocation.getLocation(), grid, space);
			context.add(car);
			car.initialize();
			CityBuilder.globalCarCount++;
		}
	}
	
	@Override
	public String toString()
	{
		return "("+location.getX()+","+location.getY()+")";
	}
	
	public Double getRed()
	{
		if(justSpawned)
			return 0.0;
		if(justAbsorbed)
			return 0.0;
		return 10*(spawnChance*50);
	}
	
	public Double getGreen()
	{
		if(justSpawned)
			return 0.0;
		if(justAbsorbed)
			return 10.0;
		return 0.0;
	}
	public Double getBlue()
	{
		if(justSpawned)
			return 10.0;
		if(justAbsorbed)
			return 0.0;
		return 10*(spawnChance*50);
	}
	
	
	public StreetCell getSpawnLocation()
	{
		return spawnLocation;
	}
	
	//removes a car when it arrives and counts it towards arrival stats
	public void removeCar(Car car)
	{
		CityBuilder.globalCarCount--;
		context.remove(car);		
		justAbsorbed = true;
		if(!space.getLocation(car.source).equals(space.getLocation(car.destination)))
		{
			double manhattanDist = (Math.abs(space.getLocation(car.source).getX()-space.getLocation(car.destination).getX())+Math.abs(space.getLocation(car.source).getY()-space.getLocation(car.destination).getY()));
			double arrivalEfficiency = 100*(manhattanDist/car.maxSpeed)/car.tickCount;
			avgArrivalEfficiency = ((double)numArrivals/((double)numArrivals+1))*avgArrivalEfficiency+(arrivalEfficiency/((double)numArrivals+1));
			numArrivals++;
		}
	}
	
	//Output for a dataset
	//returns the overall mean arrival efficiency over the life of the simulation
	//ArrivalEfficiency = ArrivalTickCount/StraightLineDistanceSourceToSink
	public static double getAvgArrivalEfficiency()
	{
		return avgArrivalEfficiency;
	}
	
}
